In this project, we tested different classification model for facial emotion recognition. Our group tried out six different machine learning algorithms, trained them on the given data set, cross-validated to find the optimized parameters, and provided fair evaluation for all the models. The evaluation process considered the prediction error (and accuracy), the area under the ROC curve (or AUC), and running time (testing and training) to choose the most improved model.
if(!require("EBImage")){
install.packages("BiocManager")
BiocManager::install("EBImage")
}
程辑包‘EBImage’是用R版本4.0.3 来建造的
if(!require("R.matlab")){
install.packages("R.matlab")
}
if(!require("readxl")){
install.packages("readxl")
}
if(!require("dplyr")){
install.packages("dplyr")
}
if(!require("readxl")){
install.packages("readxl")
}
if(!require("ggplot2")){
install.packages("ggplot2")
}
if(!require("caret")){
install.packages("caret")
}
if(!require("glmnet")){
install.packages("glmnet")
}
if(!require("WeightedROC")){
install.packages("WeightedROC")
}
if(!require("e1071")){
install.packages("e1071")
}
if(!require("xgboost")){
install.packages("xgboost")
}
if(!require("randomForest")){
install.packages("randomForest")
}
library(R.matlab)
library(readxl)
library(dplyr)
library(EBImage)
library(ggplot2)
library(caret)
library(glmnet)
library(WeightedROC)
library(e1071)
library(xgboost)
library(randomForest)
Step 0 set work directories
set.seed(2020)
# setwd("~/Project3-FacialEmotionRecognition/doc")
# here replace it with your own path or manually set it in RStudio to where this rmd file is located.
# use relative path for reproducibility
Provide directories for training images. Training images and Training fiducial points will be in different subfolders.
train_dir <- "../data/train_set/" # This will be modified for different data sets.
train_image_dir <- paste(train_dir, "images/", sep="")
train_pt_dir <- paste(train_dir, "points/", sep="")
train_label_path <- paste(train_dir, "label.csv", sep="")
Step 1: set up controls for evaluation experiments.
In this chunk, we have a set of controls for the evaluation experiments.
- (T/F) cross-validation on the training set
- (T/F) reweighting the samples for training set
- (number) K, the number of CV folds
- (T/F) process features for training set
- (T/F) run evaluation on an independent test set
- (T/F) process features for test set
run.cv <- FALSE # run cross-validation on the training set
run.cv.baseline <- FALSE # run cross-validation on the gbm baseline
sample.reweight <- TRUE # run sample reweighting in model training
K <- 5 # number of CV folds
run.feature.train <- TRUE # process features for training set
run.test <- TRUE # run evaluation on an independent test set
run.feature.test <- TRUE # process features for test set
run.cv.svm <- FALSE
run.test.svm <- TRUE
Using cross-validation or independent test set evaluation, we compare the performance of models with different specifications.
#gbm parameters tuning:
n.trees = c(10,50,100,200)
shrinkage = c(0.01,0.05,0.1,0.15)
#svm parameters tuning:
cost = c(0.0000001,0.000001,0.00001,0.0001,0.001,0.01,1)
model_labels_svm = paste("SVM with cost =", cost)
#xgboost parameters tuning
params <- list(booster = "gbtree", objective = "binary:logistic",
eta=0.3, gamma=0, max_depth=6, min_child_weight=1,
subsample=1, colsample_bytree=1)
#random forest parameters tuning
ntrees <- 128
#according to a paper by Thais Mayumi Oshiro, Pedro Santoro Perez, and Jos ́e Augusto Baranauska,
# the AUC gain for increasing number of trees is minimal after 128,
# after observing a multitude of their datasets
Step 2: import data and train-test split
#train-test split
info <- read.csv(train_label_path)
n <- nrow(info)
n_train <- round(n*(4/5), 0)
train_idx <- sample(info$Index, n_train, replace = F)
test_idx <- setdiff(info$Index, train_idx)
If you choose to extract features from images, such as using Gabor filter, R memory will exhaust all images are read together. The solution is to repeat reading a smaller batch(e.g 100) and process them.
#function to read fiducial points
#input: index
#output: matrix of fiducial points corresponding to the index
readMat.matrix <- function(index){
return(round(readMat(paste0(train_pt_dir, sprintf("%04d", index), ".mat"))[[1]],0))
}
#load fiducial points
fiducial_pt_list <- lapply(1:n_files, readMat.matrix)
save(fiducial_pt_list, file="../output/fiducial_pt_list.RData")
Fiducial points are stored in matlab format. In this step, we read them and store them in a list.
#function to read fiducial points
#input: index
#output: matrix of fiducial points corresponding to the index
readMat.matrix <- function(index){
return(round(readMat(paste0(train_pt_dir, sprintf("%04d", index), ".mat"))[[1]],0))
}
#load fiducial points
fiducial_pt_list <- lapply(1:n_files, readMat.matrix)
save(fiducial_pt_list, file="../output/fiducial_pt_list.RData")
Step 3: construct features and responses
The follow plots show how pairwise distance between fiducial points can work as feature for facial emotion recognition.
- In the first column, 78 fiducials points of each emotion are marked in order.
- In the second column distributions of vertical distance between right pupil(1) and right brow peak(21) are shown in histograms. For example, the distance of an angry face tends to be shorter than that of a surprised face.
The third column is the distributions of vertical distances between right mouth corner(50) and the midpoint of the upper lip(52). For example, the distance of an happy face tends to be shorter than that of a sad face.
feature.R should be the wrapper for all your feature engineering functions and options. The function feature( ) should have options that correspond to different scenarios for your project and produces an R object that contains features and responses that are required by all the models you are going to evaluate later.
feature.R
- Input: list of images or fiducial point
- Output: an RData file that contains extracted features and corresponding responses
source("../lib/feature.R")
tm_feature_train <- NA
if(run.feature.train){
tm_feature_train <- system.time(dat_train <- feature(fiducial_pt_list, train_idx))
save(dat_train, file="../output/feature_train.RData")
}else{
load(file="../output/feature_train.RData")
}
tm_feature_test <- NA
if(run.feature.test){
tm_feature_test <- system.time(dat_test <- feature(fiducial_pt_list, test_idx))
save(dat_test, file="../output/feature_test.RData")
}else{
load(file="../output/feature_test.RData")
}
Step 4: Train a classification model with training features and responses
Call the train model and test model from library.
train.R and test.R should be wrappers for all your model training steps and your classification/prediction steps.
train.R
- Input: a data frame containing features and labels and a parameter list.
- Output:a trained model
test.R
- Input: the fitted classification model using training data and processed features from testing images
- Input: an R object that contains a trained classifier.
- Output: training model specification
source("../lib/train.R")
source("../lib/test.R")
source("../lib/train_gbm.R")
source("../lib/test_gbm.R")
source("../lib/train_SVM.R")
source("../lib/test_SVM.R")
source("../lib/fit_train_xgboost.R")
source("../lib/fit_train_randomforest.R")
Model selection with cross-validation
- Do model selection by choosing among different values of training model parameters.
Baseline Model + Baseline/GBM
feature_train = as.matrix(dat_train[, -6007])
label_train = as.integer(dat_train$label)
source("../lib/cross_validation.R")
source("../lib/cross_validation_SVM.R")
source("../lib/cv_gbm.R")
if(run.cv.baseline){
mean_error_cv <- matrix(0, nrow = length(n.trees), ncol = length(shrinkage))
sd_error_cv <- matrix(0, nrow = length(n.trees), ncol = length(shrinkage))
mean_auc_cv <- matrix(0, nrow = length(n.trees), ncol = length(shrinkage))
sd_auc_cv <- matrix(0, nrow = length(n.trees), ncol = length(shrinkage))
for(i in 1:length(n.trees)){
cat("n.trees =", n.trees[i],"\n")
for(k in 1:length(shrinkage)){
cat("shrinkage =", shrinkage[k],"\n")
res_cv_gbm <- cv_gbm(features = feature_train, labels = label_train, K, n.trees = n.trees[i],shrinkage = shrinkage[k],reweight = sample.reweight)
mean_error_cv[i,k]<-res_cv_gbm[1]
sd_error_cv[i,k]<-res_cv_gbm[2]
mean_auc_cv[i,k]<-res_cv_gbm[3]
sd_auc_cv[i,k]<-res_cv_gbm[4]
save(mean_error_cv, file="../output/mean_error_cv.RData")
save(sd_error_cv, file="../output/sd_error_cv.RData")
save(mean_auc_cv, file="../output/mean_auc_cv.RData")
save(sd_auc_cv, file="../output/sd_auc_cv.RData")
}}
} else{
load("../output/mean_error_cv.RData")
load("../output/sd_error_cv.RData")
load("../output/mean_auc_cv.RData")
load("../output/sd_auc_cv.RData")
}
if(run.cv){
res_cv <- matrix(0, nrow = length(lmbd), ncol = 4)
for(i in 1:length(lmbd)){
cat("lambda = ", lmbd[i], "\n")
res_cv[i,] <- cv.function(features = feature_train, labels = label_train, K,
l = lmbd[i], reweight = sample.reweight)
save(res_cv, file="../output/res_cv.RData")
}
}else{
load("../output/res_cv.RData")
}
library(tidyr)
df_mean_error=data.frame(mean_error_cv)%>%
setNames(shrinkage)%>%
mutate(n.trees=n.trees)%>%
gather(shrinkage,mean_error,`0.01`:`0.15`)
df_sd_error=data.frame(sd_error_cv)%>%
setNames(shrinkage)%>%
mutate(n.trees=n.trees)%>%
gather(shrinkage,sd_error,`0.01`:`0.15`)
df_mean_auc=data.frame(mean_auc_cv)%>%
setNames(shrinkage)%>%
mutate(n.trees=n.trees)%>%
gather(shrinkage,mean_auc,`0.01`:`0.15`)
df_sd_auc=data.frame(sd_auc_cv)%>%
setNames(shrinkage)%>%
mutate(n.trees=n.trees)%>%
gather(shrinkage,sd_auc,`0.01`:`0.15`)
res_cv_gbm <- df_mean_error%>%mutate(sd_error=df_sd_error$sd_error,
mean_auc=df_mean_auc$mean_auc,
sd_auc=df_sd_auc$sd_auc)
save(res_cv_gbm,file = "../output/res_cv_gbm.RData")
if(run.cv.svm){
res_cv_svm <- matrix(0, nrow = length(cost), ncol = 4)
for(i in 1:length(cost)){
cat("cost= ", cost[i], "\n")
res_cv_svm[i,] <- svm_cv(features = feature_train, labels = label_train, K, cost=cost[i], reweight = sample.reweight)
save(res_cv_svm, file="../output/res_cv_svm.RData")
}
}else{
load("../output/res_cv_svm.RData")
}
Improved Model + XGBoost
source("../lib/xgboost_cv.R")
feature_train = as.matrix(dat_train[, -6007])
label_train = as.integer(dat_train$label)
label_train_xgb <- label_train
label_train_xgb[label_train_xgb == 2] <- 0
set_rounds <- 50
K <- 5
new_params <- cv_xgboost(params, feature_train, label_train_xgb, set_rounds, K)
save(new_params, file="../output/res_cv_xgboost.RData")
Visualize cross-validation results.
load("../output/res_cv_gbm.RData")
if(run.cv.baseline){
p1 <- res_cv_gbm %>%
ggplot(aes(x = n.trees, y = mean_error,
ymin = mean_error - sd_error, ymax = mean_error +sd_error)) +
geom_crossbar() +
facet_wrap(~shrinkage) +
theme(axis.text.x = element_text(angle = 90, hjust = 1))
p2 <- res_cv_gbm %>%
ggplot(aes(x = n.trees, y = mean_auc,
ymin = mean_auc - sd_auc, ymax = mean_auc + sd_auc)) + facet_wrap(~shrinkage) +
geom_crossbar() +
theme(axis.text.x = element_text(angle = 90, hjust = 1))
print(p1)
print(p2)
}
res_cv_svm <- as.data.frame(res_cv_svm)
colnames(res_cv_svm) <- c("mean_error", "sd_error", "mean_AUC", "sd_AUC")
res_cv_svm$k = as.factor(cost)
run.cv.svm <- TRUE
if(run.cv.svm){
p1 <- res_cv_svm %>%
ggplot(aes(x = as.factor(cost), y = mean_error,
ymin = mean_error - sd_error, ymax = mean_error + sd_error)) +
geom_crossbar() +
theme(axis.text.x = element_text(angle = 90, hjust = 1))
p2 <- res_cv_svm %>%
ggplot(aes(x = as.factor(cost), y = mean_AUC,
ymin = mean_AUC - sd_AUC, ymax = mean_AUC + sd_AUC)) +
geom_crossbar() +
theme(axis.text.x = element_text(angle = 90, hjust = 1))
print(p1)
print(p2)
}
- Choose the "best" parameter value for baseline model
tm_test_xgb = NA
feature_test <- as.matrix(dat_test[, -6007])
if(run.test){
load(file="../output/fit_train_xgb.RData")
tm_test_xgb <- system.time({label_pred_xgb <- predict(fit_train_xgb, feature_test, pred.type = 'class');
label_pred_xgb[label_pred_xgb >= 0.5] <- 1;
label_pred_xgb[label_pred_xgb < 0.5] <- 0;
prob_pred_xgb <- predict(fit_train_xgb, feature_test, pred.type = 'response')})
}
- Choose "best" number of trees and mtry for random forest model
source("../lib/rf_param_choice.R")
feature_train = as.matrix(dat_train[, -6007])
label_train = as.integer(dat_train$label)
init_mtry <- sqrt(ncol(dat_train))
rf_opt_tree <- param_choice_rf(feature_train = feature_train, label_train = label_train, mtry = init_mtry, ntree = ntrees)
save(rf_opt_tree, file = "../output/res_oob_rf.RData")
Train different models
- Train the baseline model with the entire training set using the selected model (model parameter) via cross-validation.
# training weights
weight_train <- rep(NA, length(label_train))
for (v in unique(label_train)){
weight_train[label_train == v] = 0.5 * length(label_train) / length(label_train[label_train == v])
}
if (sample.reweight){
tm_train_baseline <- system.time(fit_train_baseline <- train_gbm(feature_train, label_train, w = weight_train,best_n.trees, best_shrinkage))
} else {
tm_train_baseline <- system.time(fit_train_baseline <- train_gbm(feature_train, label_train, w = NULL, best_n.trees, best_shrinkage))
}
save(fit_train_baseline, file="../output/fit_train_baseline.RData")
- Train the SVM model with the entire training set using the selected model (model parameter) via cross-validation.
weight_train <- rep(NA, length(label_train))
for (v in unique(label_train)){
weight_train[label_train == v] = 0.5 * length(label_train) / length(label_train[label_train == v])
}
if (sample.reweight){
tm_train_svm <- system.time(fit_train_svm <-svm_train(feature_train, label_train, w = weight_train, best_cost))
} else {
tm_train_svm <- system.time(fit_train_svm <-svm_train(feature_train, label_train, w = NULL, best_cost))
}
save(fit_train_svm, file="../output/fit_train_svm.RData")
- Train the XGBoost model with optimal parameters
xgb_train_time <- system.time(fit_train_xgb <- xgboost_train(features = feature_train, labels = label_train_xgb, params = new_params, rounds = set_rounds))
save(fit_train_xgb, file="../output/fit_train_xgb.RData")
- Train the Random Forest model with optimal parameters
load(file = "../output/res_oob_rf.RData")
rf_train_time <- system.time(fit_train_randomforest <- rf_train(feature_train = feature_train, label_train = label_train, mtry = rf_opt_tree$mtry, ntree = rf_opt_tree$ntree))
save(fit_train_randomforest, file="../output/fit_train_rf.RData")
#PCA for training features
source("../lib/train_PCA.R")
# make dat_train a numeric data frame
dat_train.new <- matrix(0, ncol = ncol(dat_train) - 1, nrow = nrow(dat_train))
for (i in 1:(ncol(dat_train) - 1)) {
dat_train.new[,i] <- as.numeric(dat_train[[i]])
}
dat_train.new <- as.data.frame(dat_train.new)
#PCA for training features
tm_train_pca <- system.time(fit_train_pca <- train_pca(dat_train.new))
save(fit_train_pca, file="../output/pca_train.RData")
# determine the important principle components
screeplot(fit_train_pca, type = "l")
# The proportion of variance for first 300 PCs
sum((fit_train_pca$sdev[1:300])^2) / sum((fit_train_pca$sdev)^2)
Therefore, we can choose the first 300 principle components because they explain most of the total variance, which is around 99.9%.
# get the features of principle components with emotion index
dat_train_pca <- data.frame(fit_train_pca$x[,1:300], emotion_idx = dat_train[,6007])
Use trained PCA model to test data
source("../lib/test_PCA.R")
dat_test.new <- dat_test
colnames(dat_test.new) <- c(colnames(dat_train.new), "emotion_idx")
tm_test_pca <- system.time(dat_test.new <- test_pca(fit_train_pca, dat_test.new))
#features of testing principle components with the emotion index
dat_test_pca <- data.frame(dat_test.new[,1:300], emotion_idx = dat_test[,6007])
save(dat_train_pca, file="../output/feature_train_pca.RData")
save(dat_test_pca, file="../output/feature_test_pca.RData")
Apply LDA model
#load("../output/feature_train_pca.RData")
#train LDA model
source("../lib/train_LDA.R")
tm_train_lda <- system.time(fit_train_lda <- train_lda(dat_train_pca))
save(fit_train_lda, file="../output/LDA_train.RData")
#training accuracy in LDA model
source("../lib/test_LDA.R")
pred_train_lda <- test_lda(fit_train_lda, dat_train_pca)
accu_train_lda <- mean(pred_train_lda == dat_train_pca$emotion_idx)
cat("The trainig accuracy of model LDA", "is", accu_train_lda*100, "%.\n")
cat("Time for training model LDA = ", tm_train_lda[1], "s \n")
Step 5: Run test on test images
+Baseline model
tm_test_baseline = NA
feature_test <- as.matrix(dat_test[, -6007])
if(run.test){
load(file="../output/fit_train_baseline.RData")
tm_test_baseline <- system.time({label_pred_baseline <- as.integer(test_gbm(fit_train_baseline,feature_test,best_n.trees, best_shrinkage, pred.type = 'link'));
prob_pred_baseline <- test_gbm(fit_train_baseline, feature_test,best_n.trees, best_shrinkage, pred.type = 'response')})
}
+SVM model
tm_test = NA
feature_test <- as.matrix(dat_test[, -6007])
if(run.test.svm){
load(file="../output/fit_train_svm.RData")
tm_test_svm <- system.time({
label_pred_svm <- as.integer(svm_test(fit_train_svm, feature_test, pred.type = 'class'));
prob_pred_svm <- svm_test(fit_train_svm, feature_test, pred.type = 'response')})
}
+XGBoost
tm_test_xgb = NA
feature_test <- as.matrix(dat_test[, -6007])
if(run.test){
load(file="../output/fit_train_xgb.RData")
tm_test_xgb <- system.time({label_pred_xgb <- predict(fit_train_xgb, feature_test, pred.type = 'class');
label_pred_xgb[label_pred_xgb >= 0.5] <- 1;
label_pred_xgb[label_pred_xgb < 0.5] <- 0;
prob_pred_xgb <- predict(fit_train_xgb, feature_test, pred.type = 'response')})
}
+Random Forest
tm_test_rf = NA
feature_test <- as.matrix(dat_test[, -6007])
if(run.test){
load(file="../output/fit_train_rf.RData")
tm_test_rf <- system.time({label_pred_rf <- predict(fit_train_randomforest, feature_test, pred.type = 'class');
prob_pred_rf <- predict(fit_train_randomforest, feature_test, type = 'prob')})
}
+LDA
source("../lib/test_LDA.R")
load(file = "../output/feature_test_pca.RData")
load(file="../output/LDA_train.RData")
tm_test_lda <- system.time(pred_lda <- test_lda(fit_train_lda, dat_test_pca))
Evaluation
*Baseline Model
## reweight the test data to represent a balanced label distribution
label_test <- as.integer(dat_test$label)
weight_test <- rep(NA, length(label_test))
for (v in unique(label_test)){
weight_test[label_test == v] = 0.5 * length(label_test) / length(label_test[label_test == v])
}
accu_baseline <- mean(label_pred_baseline == label_test)
tpr.fpr.baseline <- WeightedROC(prob_pred_baseline, label_test, weight_test)
auc_baseline <- WeightedAUC(tpr.fpr.baseline)
cat("The accuracy of model GBM: with n.trees=",best_n.trees,"and shrinkage =", best_shrinkage, "is", accu_baseline*100, "%.\n")
cat("The AUC of model GBM: with n.trees=", best_n.trees,"and shrinkage =", best_shrinkage, "is", auc_baseline, ".\n")
*SVM
label_test <- as.integer(dat_test$label)
weight_test <- rep(NA, length(label_test))
for (v in unique(label_test)){
weight_test[label_test == v] = 0.5 * length(label_test) / length(label_test[label_test == v])
}
accu_svm <- mean(label_pred_svm == label_test)
tpr.fpr.svm <- WeightedROC(prob_pred_svm, label_test, weight_test)
auc_svm <- WeightedAUC(tpr.fpr.svm)
cat("The accuracy of model:", model_labels_svm[which.min(res_cv_svm$mean_error)], "is", accu_svm*100, "%.\n")
cat("The AUC of model:", model_labels_svm[which.min(res_cv_svm$mean_error)], "is", auc_svm, ".\n")
*XGBoost
label_test <- as.integer(dat_test$label)
label_test_xgb <- label_test
label_test_xgb[label_test_xgb==2] = 0
accu_xgb <- mean((label_pred_xgb == label_test_xgb))
tpr.fpr_xgb <- WeightedROC(prob_pred_xgb, label_test_xgb)
auc_xgb <- WeightedAUC(tpr.fpr_xgb)
cat("The accuracy of the XGBoost model:", "is", accu_xgb*100, "%.\n")
cat("The AUC of the XGBoost model:", "is", auc_xgb, ".\n")
*Random Forest
label_test <- as.integer(dat_test$label)
accu_rf <- mean(label_pred_rf == label_test)
tpr.fpr.rf <- WeightedROC(prob_pred_rf[,2], label_test)
auc_rf <- WeightedAUC(tpr.fpr.rf)
cat("The accuracy of the Random Forest model:", "is", accu_rf*100, "%.\n")
cat("The AUC of the Random Forest model:", "is", auc_rf, ".\n")
*LDA
accu_lda <- mean(dat_test_pca$emotion_idx == pred_lda)
label_lda <- as.numeric(dat_test_pca$emotion_idx)
tpr.fpr.lda <- WeightedROC(as.numeric(pred_lda), label_lda)
auc_lda <- WeightedAUC(tpr.fpr.lda)
cat("The accuracy of the PCA+LDA model:", "is", accu_lda*100, "%.\n")
cat("The AUC of the PCA+LDA model:", "is", auc_lda, ".\n")
Summarize Running Time
Prediction performance matters, so does the running times for constructing features and for training the model, especially when the computation resource is limited.
*Baseline Model
cat("Time for constructing training features=", tm_feature_train[1], "s \n")
cat("Time for constructing testing features=", tm_feature_test[1], "s \n")
cat("Time for training model=", tm_train_baseline[1], "s \n")
cat("Time for testing model=", tm_test_baseline[1], "s \n")
*SVM
cat("Time for constructing training features=", tm_feature_train[1], "s \n")
cat("Time for constructing testing features=", tm_feature_test[1], "s \n")
cat("Time for training model=", tm_train_svm[1], "s \n")
cat("Time for testing model=", tm_test_svm[1], "s \n")
*XGBoost
cat("Time for constructing training features=", tm_feature_train[1], "s \n")
cat("Time for constructing testing features=", tm_feature_test[1], "s \n")
cat("Time for training model=", xgb_train_time[1], "s \n")
cat("Time for testing model=", tm_test_xgb[1], "s \n")
*Random Forest
cat("Time for constructing training features=", tm_feature_train[1], "s \n")
cat("Time for constructing testing features=", tm_feature_test[1], "s \n")
cat("Time for training random forest model=", rf_train_time[1], "s \n")
cat("Time for testing random forest model=", tm_test_rf[1], "s \n")
cat("Time for constructing training features=", tm_feature_train[1], "s \n")
cat("Time for constructing testing features=", tm_feature_test[1], "s \n")
cat("Time for training PCA =", tm_train_pca[1], "s \n")
cat("Time for testing PCA =", tm_test_pca[1], "s \n")
cat("Time for training model LDA = ", tm_train_lda[1], "s \n")
cat("Time for testing model LDA = ",tm_test_lda[1], "s \n")
Reference
- Du, S., Tao, Y., & Martinez, A. M. (2014). Compound facial expressions of emotion. Proceedings of the National Academy of Sciences, 111(15), E1454-E1462.
LS0tCnRpdGxlOiAiV29ya2luZ19NYWluIgphdXRob3I6ICJEYWl6eSBMYW0sIFBldGVyIEt3YXVrLCBRaXpoZW4gWWFuZywgRWxsZW4gQ2hlbiwgRGFyeWwgS293IgpvdXRwdXQ6CiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAotLS0KCkluIHRoaXMgcHJvamVjdCwgd2UgdGVzdGVkIGRpZmZlcmVudCBjbGFzc2lmaWNhdGlvbiBtb2RlbCBmb3IgZmFjaWFsIGVtb3Rpb24gcmVjb2duaXRpb24uIE91ciBncm91cCB0cmllZCBvdXQgc2l4IGRpZmZlcmVudCBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobXMsIHRyYWluZWQgdGhlbSBvbiB0aGUgZ2l2ZW4gZGF0YSBzZXQsIGNyb3NzLXZhbGlkYXRlZCB0byBmaW5kIHRoZSBvcHRpbWl6ZWQgcGFyYW1ldGVycywgYW5kIHByb3ZpZGVkIGZhaXIgZXZhbHVhdGlvbiBmb3IgYWxsIHRoZSBtb2RlbHMuIFRoZSBldmFsdWF0aW9uIHByb2Nlc3MgY29uc2lkZXJlZCB0aGUgcHJlZGljdGlvbiBlcnJvciAoYW5kIGFjY3VyYWN5KSwgdGhlIGFyZWEgdW5kZXIgdGhlIFJPQyBjdXJ2ZSAob3IgQVVDKSwgYW5kIHJ1bm5pbmcgdGltZSAodGVzdGluZyBhbmQgdHJhaW5pbmcpIHRvIGNob29zZSB0aGUgbW9zdCBpbXByb3ZlZCBtb2RlbC4KCmBgYHtyIG1lc3NhZ2U9RkFMU0V9CmlmKCFyZXF1aXJlKCJFQkltYWdlIikpewogIGluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIikKICBCaW9jTWFuYWdlcjo6aW5zdGFsbCgiRUJJbWFnZSIpCn0KaWYoIXJlcXVpcmUoIlIubWF0bGFiIikpewogIGluc3RhbGwucGFja2FnZXMoIlIubWF0bGFiIikKfQppZighcmVxdWlyZSgicmVhZHhsIikpewogIGluc3RhbGwucGFja2FnZXMoInJlYWR4bCIpCn0KCmlmKCFyZXF1aXJlKCJkcGx5ciIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJkcGx5ciIpCn0KaWYoIXJlcXVpcmUoInJlYWR4bCIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJyZWFkeGwiKQp9CgppZighcmVxdWlyZSgiZ2dwbG90MiIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikKfQoKaWYoIXJlcXVpcmUoImNhcmV0IikpewogIGluc3RhbGwucGFja2FnZXMoImNhcmV0IikKfQoKaWYoIXJlcXVpcmUoImdsbW5ldCIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJnbG1uZXQiKQp9CgppZighcmVxdWlyZSgiV2VpZ2h0ZWRST0MiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiV2VpZ2h0ZWRST0MiKQp9CgppZighcmVxdWlyZSgiZTEwNzEiKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiZTEwNzEiKQp9CgppZighcmVxdWlyZSgieGdib29zdCIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJ4Z2Jvb3N0IikKfQoKaWYoIXJlcXVpcmUoInJhbmRvbUZvcmVzdCIpKXsKICBpbnN0YWxsLnBhY2thZ2VzKCJyYW5kb21Gb3Jlc3QiKQp9CgpsaWJyYXJ5KFIubWF0bGFiKQpsaWJyYXJ5KHJlYWR4bCkKbGlicmFyeShkcGx5cikKbGlicmFyeShFQkltYWdlKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkoZ2xtbmV0KQpsaWJyYXJ5KFdlaWdodGVkUk9DKQpsaWJyYXJ5KGUxMDcxKQpsaWJyYXJ5KHhnYm9vc3QpCmxpYnJhcnkocmFuZG9tRm9yZXN0KQpgYGAKCiMjIyBTdGVwIDAgc2V0IHdvcmsgZGlyZWN0b3JpZXMKYGBge3Igd2tkaXIsIGV2YWw9RkFMU0V9CnNldC5zZWVkKDIwMjApCiMgc2V0d2QoIn4vUHJvamVjdDMtRmFjaWFsRW1vdGlvblJlY29nbml0aW9uL2RvYyIpCiMgaGVyZSByZXBsYWNlIGl0IHdpdGggeW91ciBvd24gcGF0aCBvciBtYW51YWxseSBzZXQgaXQgaW4gUlN0dWRpbyB0byB3aGVyZSB0aGlzIHJtZCBmaWxlIGlzIGxvY2F0ZWQuIAojIHVzZSByZWxhdGl2ZSBwYXRoIGZvciByZXByb2R1Y2liaWxpdHkKYGBgCgpQcm92aWRlIGRpcmVjdG9yaWVzIGZvciB0cmFpbmluZyBpbWFnZXMuIFRyYWluaW5nIGltYWdlcyBhbmQgVHJhaW5pbmcgZmlkdWNpYWwgcG9pbnRzIHdpbGwgYmUgaW4gZGlmZmVyZW50IHN1YmZvbGRlcnMuIApgYGB7cn0KdHJhaW5fZGlyIDwtICIuLi9kYXRhL3RyYWluX3NldC8iICMgVGhpcyB3aWxsIGJlIG1vZGlmaWVkIGZvciBkaWZmZXJlbnQgZGF0YSBzZXRzLgp0cmFpbl9pbWFnZV9kaXIgPC0gcGFzdGUodHJhaW5fZGlyLCAiaW1hZ2VzLyIsIHNlcD0iIikKdHJhaW5fcHRfZGlyIDwtIHBhc3RlKHRyYWluX2RpciwgICJwb2ludHMvIiwgc2VwPSIiKQp0cmFpbl9sYWJlbF9wYXRoIDwtIHBhc3RlKHRyYWluX2RpciwgImxhYmVsLmNzdiIsIHNlcD0iIikgCmBgYAoKIyMjIFN0ZXAgMTogc2V0IHVwIGNvbnRyb2xzIGZvciBldmFsdWF0aW9uIGV4cGVyaW1lbnRzLgoKSW4gdGhpcyBjaHVuaywgd2UgaGF2ZSBhIHNldCBvZiBjb250cm9scyBmb3IgdGhlIGV2YWx1YXRpb24gZXhwZXJpbWVudHMuIAoKKyAoVC9GKSBjcm9zcy12YWxpZGF0aW9uIG9uIHRoZSB0cmFpbmluZyBzZXQKKyAoVC9GKSByZXdlaWdodGluZyB0aGUgc2FtcGxlcyBmb3IgdHJhaW5pbmcgc2V0IAorIChudW1iZXIpIEssIHRoZSBudW1iZXIgb2YgQ1YgZm9sZHMKKyAoVC9GKSBwcm9jZXNzIGZlYXR1cmVzIGZvciB0cmFpbmluZyBzZXQKKyAoVC9GKSBydW4gZXZhbHVhdGlvbiBvbiBhbiBpbmRlcGVuZGVudCB0ZXN0IHNldAorIChUL0YpIHByb2Nlc3MgZmVhdHVyZXMgZm9yIHRlc3Qgc2V0CgpgYGB7ciBleHBfc2V0dXB9CnJ1bi5jdiA8LSBGQUxTRSAjIHJ1biBjcm9zcy12YWxpZGF0aW9uIG9uIHRoZSB0cmFpbmluZyBzZXQKcnVuLmN2LmJhc2VsaW5lIDwtIEZBTFNFICMgcnVuIGNyb3NzLXZhbGlkYXRpb24gb24gdGhlIGdibSBiYXNlbGluZQpzYW1wbGUucmV3ZWlnaHQgPC0gVFJVRSAjIHJ1biBzYW1wbGUgcmV3ZWlnaHRpbmcgaW4gbW9kZWwgdHJhaW5pbmcKSyA8LSA1ICAjIG51bWJlciBvZiBDViBmb2xkcwpydW4uZmVhdHVyZS50cmFpbiA8LSBUUlVFICMgcHJvY2VzcyBmZWF0dXJlcyBmb3IgdHJhaW5pbmcgc2V0CnJ1bi50ZXN0IDwtIFRSVUUgIyBydW4gZXZhbHVhdGlvbiBvbiBhbiBpbmRlcGVuZGVudCB0ZXN0IHNldApydW4uZmVhdHVyZS50ZXN0IDwtIFRSVUUgIyBwcm9jZXNzIGZlYXR1cmVzIGZvciB0ZXN0IHNldApydW4uY3Yuc3ZtIDwtIEZBTFNFCnJ1bi50ZXN0LnN2bSA8LSBUUlVFCgpgYGAKClVzaW5nIGNyb3NzLXZhbGlkYXRpb24gb3IgaW5kZXBlbmRlbnQgdGVzdCBzZXQgZXZhbHVhdGlvbiwgd2UgY29tcGFyZSB0aGUgcGVyZm9ybWFuY2Ugb2YgbW9kZWxzIHdpdGggZGlmZmVyZW50IHNwZWNpZmljYXRpb25zLgoKYGBge3IgbW9kZWxfc2V0dXB9CgojZ2JtIHBhcmFtZXRlcnMgdHVuaW5nOgpuLnRyZWVzID0gYygxMCw1MCwxMDAsMjAwKQpzaHJpbmthZ2UgPSBjKDAuMDEsMC4wNSwwLjEsMC4xNSkKCiNzdm0gcGFyYW1ldGVycyB0dW5pbmc6CmNvc3QgPSBjKDAuMDAwMDAwMSwwLjAwMDAwMSwwLjAwMDAxLDAuMDAwMSwwLjAwMSwwLjAxLDEpCm1vZGVsX2xhYmVsc19zdm0gPSBwYXN0ZSgiU1ZNIHdpdGggY29zdCA9IiwgY29zdCkKCiN4Z2Jvb3N0IHBhcmFtZXRlcnMgdHVuaW5nCnBhcmFtcyA8LSBsaXN0KGJvb3N0ZXIgPSAiZ2J0cmVlIiwgb2JqZWN0aXZlID0gImJpbmFyeTpsb2dpc3RpYyIsIAogICAgICAgICAgICAgICAgIGV0YT0wLjMsIGdhbW1hPTAsIG1heF9kZXB0aD02LCBtaW5fY2hpbGRfd2VpZ2h0PTEsIAogICAgICAgICAgICAgICAgIHN1YnNhbXBsZT0xLCBjb2xzYW1wbGVfYnl0cmVlPTEpCgojcmFuZG9tIGZvcmVzdCBwYXJhbWV0ZXJzIHR1bmluZwpudHJlZXMgPC0gMTI4IAojYWNjb3JkaW5nIHRvIGEgcGFwZXIgYnkgVGhhaXMgTWF5dW1pIE9zaGlybywgUGVkcm8gU2FudG9ybyBQZXJleiwgYW5kIEpvcyDMgWUgQXVndXN0byBCYXJhbmF1c2thLAojIHRoZSBBVUMgZ2FpbiBmb3IgaW5jcmVhc2luZyBudW1iZXIgb2YgdHJlZXMgaXMgbWluaW1hbCBhZnRlciAxMjgsIAojIGFmdGVyIG9ic2VydmluZyBhIG11bHRpdHVkZSBvZiB0aGVpciBkYXRhc2V0cwoKCmBgYAoKIyMjIFN0ZXAgMjogaW1wb3J0IGRhdGEgYW5kIHRyYWluLXRlc3Qgc3BsaXQgCmBgYHtyfQojdHJhaW4tdGVzdCBzcGxpdAppbmZvIDwtIHJlYWQuY3N2KHRyYWluX2xhYmVsX3BhdGgpCm4gPC0gbnJvdyhpbmZvKQpuX3RyYWluIDwtIHJvdW5kKG4qKDQvNSksIDApCnRyYWluX2lkeCA8LSBzYW1wbGUoaW5mbyRJbmRleCwgbl90cmFpbiwgcmVwbGFjZSA9IEYpCnRlc3RfaWR4IDwtIHNldGRpZmYoaW5mbyRJbmRleCwgdHJhaW5faWR4KQpgYGAKCklmIHlvdSBjaG9vc2UgdG8gZXh0cmFjdCBmZWF0dXJlcyBmcm9tIGltYWdlcywgc3VjaCBhcyB1c2luZyBHYWJvciBmaWx0ZXIsIFIgbWVtb3J5IHdpbGwgZXhoYXVzdCBhbGwgaW1hZ2VzIGFyZSByZWFkIHRvZ2V0aGVyLiBUaGUgc29sdXRpb24gaXMgdG8gcmVwZWF0IHJlYWRpbmcgYSBzbWFsbGVyIGJhdGNoKGUuZyAxMDApIGFuZCBwcm9jZXNzIHRoZW0uIApgYGB7cn0Kbl9maWxlcyA8LSBsZW5ndGgobGlzdC5maWxlcyh0cmFpbl9pbWFnZV9kaXIpKQoKaW1hZ2VfbGlzdCA8LSBsaXN0KCkKZm9yKGkgaW4gMToxMDApewogICBpbWFnZV9saXN0W1tpXV0gPC0gcmVhZEltYWdlKHBhc3RlMCh0cmFpbl9pbWFnZV9kaXIsIHNwcmludGYoIiUwNGQiLCBpKSwgIi5qcGciKSkKfQpgYGAKCkZpZHVjaWFsIHBvaW50cyBhcmUgc3RvcmVkIGluIG1hdGxhYiBmb3JtYXQuIEluIHRoaXMgc3RlcCwgd2UgcmVhZCB0aGVtIGFuZCBzdG9yZSB0aGVtIGluIGEgbGlzdC4KYGBge3IgcmVhZCBmaWR1Y2lhbCBwb2ludHN9CiNmdW5jdGlvbiB0byByZWFkIGZpZHVjaWFsIHBvaW50cwojaW5wdXQ6IGluZGV4CiNvdXRwdXQ6IG1hdHJpeCBvZiBmaWR1Y2lhbCBwb2ludHMgY29ycmVzcG9uZGluZyB0byB0aGUgaW5kZXgKcmVhZE1hdC5tYXRyaXggPC0gZnVuY3Rpb24oaW5kZXgpewogICAgIHJldHVybihyb3VuZChyZWFkTWF0KHBhc3RlMCh0cmFpbl9wdF9kaXIsIHNwcmludGYoIiUwNGQiLCBpbmRleCksICIubWF0IikpW1sxXV0sMCkpCn0KCiNsb2FkIGZpZHVjaWFsIHBvaW50cwpmaWR1Y2lhbF9wdF9saXN0IDwtIGxhcHBseSgxOm5fZmlsZXMsIHJlYWRNYXQubWF0cml4KQpzYXZlKGZpZHVjaWFsX3B0X2xpc3QsIGZpbGU9Ii4uL291dHB1dC9maWR1Y2lhbF9wdF9saXN0LlJEYXRhIikKYGBgCgojIyMgU3RlcCAzOiBjb25zdHJ1Y3QgZmVhdHVyZXMgYW5kIHJlc3BvbnNlcwoKKyBUaGUgZm9sbG93IHBsb3RzIHNob3cgaG93IHBhaXJ3aXNlIGRpc3RhbmNlIGJldHdlZW4gZmlkdWNpYWwgcG9pbnRzIGNhbiB3b3JrIGFzIGZlYXR1cmUgZm9yIGZhY2lhbCBlbW90aW9uIHJlY29nbml0aW9uLgoKICArIEluIHRoZSBmaXJzdCBjb2x1bW4sIDc4IGZpZHVjaWFscyBwb2ludHMgb2YgZWFjaCBlbW90aW9uIGFyZSBtYXJrZWQgaW4gb3JkZXIuIAogICsgSW4gdGhlIHNlY29uZCBjb2x1bW4gZGlzdHJpYnV0aW9ucyBvZiB2ZXJ0aWNhbCBkaXN0YW5jZSBiZXR3ZWVuIHJpZ2h0IHB1cGlsKDEpIGFuZCAgcmlnaHQgYnJvdyBwZWFrKDIxKSBhcmUgc2hvd24gaW4gIGhpc3RvZ3JhbXMuIEZvciBleGFtcGxlLCB0aGUgZGlzdGFuY2Ugb2YgYW4gYW5ncnkgZmFjZSB0ZW5kcyB0byBiZSBzaG9ydGVyIHRoYW4gdGhhdCBvZiBhIHN1cnByaXNlZCBmYWNlLgogICsgVGhlIHRoaXJkIGNvbHVtbiBpcyB0aGUgZGlzdHJpYnV0aW9ucyBvZiB2ZXJ0aWNhbCBkaXN0YW5jZXMgYmV0d2VlbiByaWdodCBtb3V0aCBjb3JuZXIoNTApCmFuZCB0aGUgbWlkcG9pbnQgb2YgdGhlIHVwcGVyIGxpcCg1MikuICBGb3IgZXhhbXBsZSwgdGhlIGRpc3RhbmNlIG9mIGFuIGhhcHB5IGZhY2UgdGVuZHMgdG8gYmUgc2hvcnRlciB0aGFuIHRoYXQgb2YgYSBzYWQgZmFjZS4KCiFbRmlndXJlMV0oLi4vZmlncy9mZWF0dXJlX3Zpc3VhbGl6YXRpb24uanBnKQoKYGZlYXR1cmUuUmAgc2hvdWxkIGJlIHRoZSB3cmFwcGVyIGZvciBhbGwgeW91ciBmZWF0dXJlIGVuZ2luZWVyaW5nIGZ1bmN0aW9ucyBhbmQgb3B0aW9ucy4gVGhlIGZ1bmN0aW9uIGBmZWF0dXJlKCApYCBzaG91bGQgaGF2ZSBvcHRpb25zIHRoYXQgY29ycmVzcG9uZCB0byBkaWZmZXJlbnQgc2NlbmFyaW9zIGZvciB5b3VyIHByb2plY3QgYW5kIHByb2R1Y2VzIGFuIFIgb2JqZWN0IHRoYXQgY29udGFpbnMgZmVhdHVyZXMgYW5kIHJlc3BvbnNlcyB0aGF0IGFyZSByZXF1aXJlZCBieSBhbGwgdGhlIG1vZGVscyB5b3UgYXJlIGdvaW5nIHRvIGV2YWx1YXRlIGxhdGVyLiAKICAKICArIGBmZWF0dXJlLlJgCiAgKyBJbnB1dDogbGlzdCBvZiBpbWFnZXMgb3IgZmlkdWNpYWwgcG9pbnQKICArIE91dHB1dDogYW4gUkRhdGEgZmlsZSB0aGF0IGNvbnRhaW5zIGV4dHJhY3RlZCBmZWF0dXJlcyBhbmQgY29ycmVzcG9uZGluZyByZXNwb25zZXMKCmBgYHtyIGZlYXR1cmV9CnNvdXJjZSgiLi4vbGliL2ZlYXR1cmUuUiIpCnRtX2ZlYXR1cmVfdHJhaW4gPC0gTkEKaWYocnVuLmZlYXR1cmUudHJhaW4pewogIHRtX2ZlYXR1cmVfdHJhaW4gPC0gc3lzdGVtLnRpbWUoZGF0X3RyYWluIDwtIGZlYXR1cmUoZmlkdWNpYWxfcHRfbGlzdCwgdHJhaW5faWR4KSkKICBzYXZlKGRhdF90cmFpbiwgZmlsZT0iLi4vb3V0cHV0L2ZlYXR1cmVfdHJhaW4uUkRhdGEiKQp9ZWxzZXsKICBsb2FkKGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3RyYWluLlJEYXRhIikKfQoKdG1fZmVhdHVyZV90ZXN0IDwtIE5BCmlmKHJ1bi5mZWF0dXJlLnRlc3QpewogIHRtX2ZlYXR1cmVfdGVzdCA8LSBzeXN0ZW0udGltZShkYXRfdGVzdCA8LSBmZWF0dXJlKGZpZHVjaWFsX3B0X2xpc3QsIHRlc3RfaWR4KSkKICBzYXZlKGRhdF90ZXN0LCBmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV90ZXN0LlJEYXRhIikKfWVsc2V7CiAgbG9hZChmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV90ZXN0LlJEYXRhIikKfQoKCmBgYAoKIyMjIFN0ZXAgNDogVHJhaW4gYSBjbGFzc2lmaWNhdGlvbiBtb2RlbCB3aXRoIHRyYWluaW5nIGZlYXR1cmVzIGFuZCByZXNwb25zZXMKQ2FsbCB0aGUgdHJhaW4gbW9kZWwgYW5kIHRlc3QgbW9kZWwgZnJvbSBsaWJyYXJ5LiAKCmB0cmFpbi5SYCBhbmQgYHRlc3QuUmAgc2hvdWxkIGJlIHdyYXBwZXJzIGZvciBhbGwgeW91ciBtb2RlbCB0cmFpbmluZyBzdGVwcyBhbmQgeW91ciBjbGFzc2lmaWNhdGlvbi9wcmVkaWN0aW9uIHN0ZXBzLiAKCisgYHRyYWluLlJgCiAgKyBJbnB1dDogYSBkYXRhIGZyYW1lIGNvbnRhaW5pbmcgZmVhdHVyZXMgYW5kIGxhYmVscyBhbmQgYSBwYXJhbWV0ZXIgbGlzdC4KICArIE91dHB1dDphIHRyYWluZWQgbW9kZWwKKyBgdGVzdC5SYAogICsgSW5wdXQ6IHRoZSBmaXR0ZWQgY2xhc3NpZmljYXRpb24gbW9kZWwgdXNpbmcgdHJhaW5pbmcgZGF0YSBhbmQgcHJvY2Vzc2VkIGZlYXR1cmVzIGZyb20gdGVzdGluZyBpbWFnZXMgCiAgKyBJbnB1dDogYW4gUiBvYmplY3QgdGhhdCBjb250YWlucyBhIHRyYWluZWQgY2xhc3NpZmllci4KICArIE91dHB1dDogdHJhaW5pbmcgbW9kZWwgc3BlY2lmaWNhdGlvbgoKCmBgYHtyIGxvYWRsaWJ9CnNvdXJjZSgiLi4vbGliL3RyYWluLlIiKSAKc291cmNlKCIuLi9saWIvdGVzdC5SIikKCnNvdXJjZSgiLi4vbGliL3RyYWluX2dibS5SIikKc291cmNlKCIuLi9saWIvdGVzdF9nYm0uUiIpCgpzb3VyY2UoIi4uL2xpYi90cmFpbl9TVk0uUiIpIApzb3VyY2UoIi4uL2xpYi90ZXN0X1NWTS5SIikKCnNvdXJjZSgiLi4vbGliL2ZpdF90cmFpbl94Z2Jvb3N0LlIiKQpzb3VyY2UoIi4uL2xpYi9maXRfdHJhaW5fcmFuZG9tZm9yZXN0LlIiKQpgYGAKCiMjIyMgTW9kZWwgc2VsZWN0aW9uIHdpdGggY3Jvc3MtdmFsaWRhdGlvbgoqIERvIG1vZGVsIHNlbGVjdGlvbiBieSBjaG9vc2luZyBhbW9uZyBkaWZmZXJlbnQgdmFsdWVzIG9mIHRyYWluaW5nIG1vZGVsIHBhcmFtZXRlcnMuCgoqKkJhc2VsaW5lIE1vZGVsKioKKyBCYXNlbGluZS9HQk0KYGBge3IgcnVuY3Z9CmZlYXR1cmVfdHJhaW4gPSBhcy5tYXRyaXgoZGF0X3RyYWluWywgLTYwMDddKQpsYWJlbF90cmFpbiA9IGFzLmludGVnZXIoZGF0X3RyYWluJGxhYmVsKSAKCnNvdXJjZSgiLi4vbGliL2Nyb3NzX3ZhbGlkYXRpb24uUiIpCnNvdXJjZSgiLi4vbGliL2Nyb3NzX3ZhbGlkYXRpb25fU1ZNLlIiKQpzb3VyY2UoIi4uL2xpYi9jdl9nYm0uUiIpCgppZihydW4uY3YuYmFzZWxpbmUpeyAgCiAgCiAgbWVhbl9lcnJvcl9jdiA8LSBtYXRyaXgoMCwgbnJvdyA9IGxlbmd0aChuLnRyZWVzKSwgbmNvbCA9IGxlbmd0aChzaHJpbmthZ2UpKQogIHNkX2Vycm9yX2N2IDwtIG1hdHJpeCgwLCBucm93ID0gbGVuZ3RoKG4udHJlZXMpLCBuY29sID0gbGVuZ3RoKHNocmlua2FnZSkpCiAgbWVhbl9hdWNfY3YgPC0gbWF0cml4KDAsIG5yb3cgPSBsZW5ndGgobi50cmVlcyksIG5jb2wgPSBsZW5ndGgoc2hyaW5rYWdlKSkKICBzZF9hdWNfY3YgPC0gbWF0cml4KDAsIG5yb3cgPSBsZW5ndGgobi50cmVlcyksIG5jb2wgPSBsZW5ndGgoc2hyaW5rYWdlKSkKCiAgZm9yKGkgaW4gMTpsZW5ndGgobi50cmVlcykpewogICAgY2F0KCJuLnRyZWVzID0iLCBuLnRyZWVzW2ldLCJcbiIpCiAgZm9yKGsgaW4gMTpsZW5ndGgoc2hyaW5rYWdlKSl7CiAgICBjYXQoInNocmlua2FnZSA9Iiwgc2hyaW5rYWdlW2tdLCJcbiIpCiAgICAKcmVzX2N2X2dibSA8LSBjdl9nYm0oZmVhdHVyZXMgPSBmZWF0dXJlX3RyYWluLCBsYWJlbHMgPSBsYWJlbF90cmFpbiwgSywgIG4udHJlZXMgPSBuLnRyZWVzW2ldLHNocmlua2FnZSA9IHNocmlua2FnZVtrXSxyZXdlaWdodCA9IHNhbXBsZS5yZXdlaWdodCkKICAgICAgCiAgICBtZWFuX2Vycm9yX2N2W2ksa108LXJlc19jdl9nYm1bMV0KICAgICBzZF9lcnJvcl9jdltpLGtdPC1yZXNfY3ZfZ2JtWzJdCiAgICAgIG1lYW5fYXVjX2N2W2ksa108LXJlc19jdl9nYm1bM10KICAgICAgIHNkX2F1Y19jdltpLGtdPC1yZXNfY3ZfZ2JtWzRdCiAgICAgIAogICAgc2F2ZShtZWFuX2Vycm9yX2N2LCBmaWxlPSIuLi9vdXRwdXQvbWVhbl9lcnJvcl9jdi5SRGF0YSIpCiAgICBzYXZlKHNkX2Vycm9yX2N2LCBmaWxlPSIuLi9vdXRwdXQvc2RfZXJyb3JfY3YuUkRhdGEiKQogICAgc2F2ZShtZWFuX2F1Y19jdiwgZmlsZT0iLi4vb3V0cHV0L21lYW5fYXVjX2N2LlJEYXRhIikKICAgIHNhdmUoc2RfYXVjX2N2LCBmaWxlPSIuLi9vdXRwdXQvc2RfYXVjX2N2LlJEYXRhIikgCiAgICB9fQogfSBlbHNlewogIGxvYWQoIi4uL291dHB1dC9tZWFuX2Vycm9yX2N2LlJEYXRhIikKICBsb2FkKCIuLi9vdXRwdXQvc2RfZXJyb3JfY3YuUkRhdGEiKSAgIAogIGxvYWQoIi4uL291dHB1dC9tZWFuX2F1Y19jdi5SRGF0YSIpCiAgbG9hZCgiLi4vb3V0cHV0L3NkX2F1Y19jdi5SRGF0YSIpCiAgICB9CgppZihydW4uY3YpewogIHJlc19jdiA8LSBtYXRyaXgoMCwgbnJvdyA9IGxlbmd0aChsbWJkKSwgbmNvbCA9IDQpCiAgZm9yKGkgaW4gMTpsZW5ndGgobG1iZCkpewogICAgY2F0KCJsYW1iZGEgPSAiLCBsbWJkW2ldLCAiXG4iKQogICAgcmVzX2N2W2ksXSA8LSBjdi5mdW5jdGlvbihmZWF0dXJlcyA9IGZlYXR1cmVfdHJhaW4sIGxhYmVscyA9IGxhYmVsX3RyYWluLCBLLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbCA9IGxtYmRbaV0sIHJld2VpZ2h0ID0gc2FtcGxlLnJld2VpZ2h0KQogIHNhdmUocmVzX2N2LCBmaWxlPSIuLi9vdXRwdXQvcmVzX2N2LlJEYXRhIikKICB9Cn1lbHNlewogIGxvYWQoIi4uL291dHB1dC9yZXNfY3YuUkRhdGEiKQp9CmBgYAoKYGBge3IgY3YgcmVzdWx0IGRmfQpsaWJyYXJ5KHRpZHlyKQoKZGZfbWVhbl9lcnJvcj1kYXRhLmZyYW1lKG1lYW5fZXJyb3JfY3YpJT4lCnNldE5hbWVzKHNocmlua2FnZSklPiUKbXV0YXRlKG4udHJlZXM9bi50cmVlcyklPiUKZ2F0aGVyKHNocmlua2FnZSxtZWFuX2Vycm9yLGAwLjAxYDpgMC4xNWApCgpkZl9zZF9lcnJvcj1kYXRhLmZyYW1lKHNkX2Vycm9yX2N2KSU+JQpzZXROYW1lcyhzaHJpbmthZ2UpJT4lCm11dGF0ZShuLnRyZWVzPW4udHJlZXMpJT4lCmdhdGhlcihzaHJpbmthZ2Usc2RfZXJyb3IsYDAuMDFgOmAwLjE1YCkKCmRmX21lYW5fYXVjPWRhdGEuZnJhbWUobWVhbl9hdWNfY3YpJT4lCnNldE5hbWVzKHNocmlua2FnZSklPiUKbXV0YXRlKG4udHJlZXM9bi50cmVlcyklPiUKZ2F0aGVyKHNocmlua2FnZSxtZWFuX2F1YyxgMC4wMWA6YDAuMTVgKQoKZGZfc2RfYXVjPWRhdGEuZnJhbWUoc2RfYXVjX2N2KSU+JQpzZXROYW1lcyhzaHJpbmthZ2UpJT4lCm11dGF0ZShuLnRyZWVzPW4udHJlZXMpJT4lCmdhdGhlcihzaHJpbmthZ2Usc2RfYXVjLGAwLjAxYDpgMC4xNWApCgpyZXNfY3ZfZ2JtIDwtIGRmX21lYW5fZXJyb3IlPiVtdXRhdGUoc2RfZXJyb3I9ZGZfc2RfZXJyb3Ikc2RfZXJyb3IsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZWFuX2F1Yz1kZl9tZWFuX2F1YyRtZWFuX2F1YywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNkX2F1Yz1kZl9zZF9hdWMkc2RfYXVjKQpzYXZlKHJlc19jdl9nYm0sZmlsZSA9ICIuLi9vdXRwdXQvcmVzX2N2X2dibS5SRGF0YSIpCmBgYAoKKyBTVk0KYGBge3Igc3ZtfQppZihydW4uY3Yuc3ZtKXsKICByZXNfY3Zfc3ZtIDwtIG1hdHJpeCgwLCBucm93ID0gbGVuZ3RoKGNvc3QpLCBuY29sID0gNCkKICBmb3IoaSBpbiAxOmxlbmd0aChjb3N0KSl7CiAgICBjYXQoImNvc3Q9ICIsIGNvc3RbaV0sICJcbiIpCiAgICByZXNfY3Zfc3ZtW2ksXSA8LSBzdm1fY3YoZmVhdHVyZXMgPSBmZWF0dXJlX3RyYWluLCBsYWJlbHMgPSBsYWJlbF90cmFpbiwgSywgY29zdD1jb3N0W2ldLCByZXdlaWdodCA9IHNhbXBsZS5yZXdlaWdodCkKICBzYXZlKHJlc19jdl9zdm0sIGZpbGU9Ii4uL291dHB1dC9yZXNfY3Zfc3ZtLlJEYXRhIikKICB9Cn1lbHNlewogIGxvYWQoIi4uL291dHB1dC9yZXNfY3Zfc3ZtLlJEYXRhIikKfQpgYGAKCioqSW1wcm92ZWQgTW9kZWwqKgorIFhHQm9vc3QgCmBgYHtyIFBhcmFtZXRlciBPcHRpbWl6YXRpb24gVGhyb3VnaCBDcm9zcyBWYWxpZGF0aW9uIGZvciBYR0Jvb3N0fQoKc291cmNlKCIuLi9saWIveGdib29zdF9jdi5SIikKZmVhdHVyZV90cmFpbiA9IGFzLm1hdHJpeChkYXRfdHJhaW5bLCAtNjAwN10pCmxhYmVsX3RyYWluID0gYXMuaW50ZWdlcihkYXRfdHJhaW4kbGFiZWwpIApsYWJlbF90cmFpbl94Z2IgPC0gbGFiZWxfdHJhaW4KbGFiZWxfdHJhaW5feGdiW2xhYmVsX3RyYWluX3hnYiA9PSAyXSA8LSAwCgpzZXRfcm91bmRzICA8LSA1MApLIDwtIDUKCm5ld19wYXJhbXMgPC0gY3ZfeGdib29zdChwYXJhbXMsIGZlYXR1cmVfdHJhaW4sIGxhYmVsX3RyYWluX3hnYiwgc2V0X3JvdW5kcywgSykKCnNhdmUobmV3X3BhcmFtcywgZmlsZT0iLi4vb3V0cHV0L3Jlc19jdl94Z2Jvb3N0LlJEYXRhIikKCmBgYCAKClZpc3VhbGl6ZSBjcm9zcy12YWxpZGF0aW9uIHJlc3VsdHMuIApgYGB7ciBjdl92aXN9CmxvYWQoIi4uL291dHB1dC9yZXNfY3ZfZ2JtLlJEYXRhIikKCgppZihydW4uY3YuYmFzZWxpbmUpewogIHAxIDwtIHJlc19jdl9nYm0gJT4lIAogICAgZ2dwbG90KGFlcyh4ID0gbi50cmVlcywgeSA9IG1lYW5fZXJyb3IsCiAgICAgICAgICAgICAgIHltaW4gPSBtZWFuX2Vycm9yIC0gc2RfZXJyb3IsIHltYXggPSBtZWFuX2Vycm9yICArc2RfZXJyb3IpKSArIAogICAgZ2VvbV9jcm9zc2JhcigpICsKICAgIGZhY2V0X3dyYXAofnNocmlua2FnZSkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxKSkKICAKICBwMiA8LSByZXNfY3ZfZ2JtICU+JSAKICAgIGdncGxvdChhZXMoeCA9IG4udHJlZXMsIHkgPSBtZWFuX2F1YywKICAgICAgICAgICAgICAgeW1pbiA9IG1lYW5fYXVjIC0gc2RfYXVjLCB5bWF4ID0gbWVhbl9hdWMgKyBzZF9hdWMpKSArICAgICBmYWNldF93cmFwKH5zaHJpbmthZ2UpICsKICAgIGdlb21fY3Jvc3NiYXIoKSArCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKQogIAogIHByaW50KHAxKQogIHByaW50KHAyKQp9CgpyZXNfY3Zfc3ZtIDwtIGFzLmRhdGEuZnJhbWUocmVzX2N2X3N2bSkgCmNvbG5hbWVzKHJlc19jdl9zdm0pIDwtIGMoIm1lYW5fZXJyb3IiLCAic2RfZXJyb3IiLCAibWVhbl9BVUMiLCAic2RfQVVDIikKcmVzX2N2X3N2bSRrID0gYXMuZmFjdG9yKGNvc3QpCnJ1bi5jdi5zdm0gPC0gVFJVRQppZihydW4uY3Yuc3ZtKXsKICBwMSA8LSByZXNfY3Zfc3ZtICU+JSAKICAgIGdncGxvdChhZXMoeCA9IGFzLmZhY3Rvcihjb3N0KSwgeSA9IG1lYW5fZXJyb3IsCiAgICAgICAgICAgICAgIHltaW4gPSBtZWFuX2Vycm9yIC0gc2RfZXJyb3IsIHltYXggPSBtZWFuX2Vycm9yICsgc2RfZXJyb3IpKSArIAogICAgZ2VvbV9jcm9zc2JhcigpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpCiAgCiAgcDIgPC0gcmVzX2N2X3N2bSAlPiUgCiAgICBnZ3Bsb3QoYWVzKHggPSBhcy5mYWN0b3IoY29zdCksIHkgPSBtZWFuX0FVQywKICAgICAgICAgICAgICAgeW1pbiA9IG1lYW5fQVVDIC0gc2RfQVVDLCB5bWF4ID0gbWVhbl9BVUMgKyBzZF9BVUMpKSArIAogICAgZ2VvbV9jcm9zc2JhcigpICsKICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpCiAgCiAgcHJpbnQocDEpCiAgcHJpbnQocDIpCn0KYGBgCgoKKiBDaG9vc2UgdGhlICJiZXN0IiBwYXJhbWV0ZXIgdmFsdWUgZm9yIGJhc2VsaW5lIG1vZGVsCmBgYHtyIGJlc3RfbW9kZWx9CmJlc3Rfbi50cmVlcyA9IGFzLm51bWVyaWMocmVzX2N2X2dibVt3aGljaC5taW4ocmVzX2N2X2dibSRtZWFuX2Vycm9yKSwxXSkKYmVzdF9zaHJpbmthZ2UgPSBhcy5udW1lcmljKHJlc19jdl9nYm1bd2hpY2gubWluKHJlc19jdl9nYm0kbWVhbl9lcnJvciksMl0pCgpiZXN0X2Nvc3QgPC0gY29zdFt3aGljaC5taW4ocmVzX2N2X3N2bSRtZWFuX2Vycm9yKV0KYGBgCgoqIENob29zZSAiYmVzdCIgbnVtYmVyIG9mIHRyZWVzIGFuZCBtdHJ5IGZvciByYW5kb20gZm9yZXN0IG1vZGVsCmBgYHtyIGJlc3RfbW9kZWxfcmZ9CnNvdXJjZSgiLi4vbGliL3JmX3BhcmFtX2Nob2ljZS5SIikKZmVhdHVyZV90cmFpbiA9IGFzLm1hdHJpeChkYXRfdHJhaW5bLCAtNjAwN10pCmxhYmVsX3RyYWluID0gYXMuaW50ZWdlcihkYXRfdHJhaW4kbGFiZWwpIAppbml0X210cnkgPC0gc3FydChuY29sKGRhdF90cmFpbikpCgpyZl9vcHRfdHJlZSA8LSBwYXJhbV9jaG9pY2VfcmYoZmVhdHVyZV90cmFpbiA9IGZlYXR1cmVfdHJhaW4sIGxhYmVsX3RyYWluID0gIGxhYmVsX3RyYWluLCBtdHJ5ID0gaW5pdF9tdHJ5LCBudHJlZSA9IG50cmVlcykKCnNhdmUocmZfb3B0X3RyZWUsIGZpbGUgPSAiLi4vb3V0cHV0L3Jlc19vb2JfcmYuUkRhdGEiKQpgYGAKCgojIyMjIFRyYWluIGRpZmZlcmVudCBtb2RlbHMKKyBUcmFpbiB0aGUgYmFzZWxpbmUgbW9kZWwgd2l0aCB0aGUgZW50aXJlIHRyYWluaW5nIHNldCB1c2luZyB0aGUgc2VsZWN0ZWQgbW9kZWwgKG1vZGVsIHBhcmFtZXRlcikgdmlhIGNyb3NzLXZhbGlkYXRpb24uCmBgYHtyIGZpbmFsX3RyYWluX2Jhc2VsaW5lfQojIHRyYWluaW5nIHdlaWdodHMKd2VpZ2h0X3RyYWluIDwtIHJlcChOQSwgbGVuZ3RoKGxhYmVsX3RyYWluKSkKZm9yICh2IGluIHVuaXF1ZShsYWJlbF90cmFpbikpewogIHdlaWdodF90cmFpbltsYWJlbF90cmFpbiA9PSB2XSA9IDAuNSAqIGxlbmd0aChsYWJlbF90cmFpbikgLyBsZW5ndGgobGFiZWxfdHJhaW5bbGFiZWxfdHJhaW4gPT0gdl0pCn0KaWYgKHNhbXBsZS5yZXdlaWdodCl7CiAgdG1fdHJhaW5fYmFzZWxpbmUgPC0gc3lzdGVtLnRpbWUoZml0X3RyYWluX2Jhc2VsaW5lIDwtIHRyYWluX2dibShmZWF0dXJlX3RyYWluLCBsYWJlbF90cmFpbiwgdyA9IHdlaWdodF90cmFpbixiZXN0X24udHJlZXMsIGJlc3Rfc2hyaW5rYWdlKSkKfSBlbHNlIHsKICB0bV90cmFpbl9iYXNlbGluZSA8LSBzeXN0ZW0udGltZShmaXRfdHJhaW5fYmFzZWxpbmUgPC0gdHJhaW5fZ2JtKGZlYXR1cmVfdHJhaW4sIGxhYmVsX3RyYWluLCB3ID0gTlVMTCwgYmVzdF9uLnRyZWVzLCBiZXN0X3Nocmlua2FnZSkpCn0Kc2F2ZShmaXRfdHJhaW5fYmFzZWxpbmUsIGZpbGU9Ii4uL291dHB1dC9maXRfdHJhaW5fYmFzZWxpbmUuUkRhdGEiKQpgYGAKCisgVHJhaW4gdGhlIFNWTSBtb2RlbCB3aXRoIHRoZSBlbnRpcmUgdHJhaW5pbmcgc2V0IHVzaW5nIHRoZSBzZWxlY3RlZCBtb2RlbCAobW9kZWwgcGFyYW1ldGVyKSB2aWEgY3Jvc3MtdmFsaWRhdGlvbi4KYGBge3IgZmluYWxfdHJhaW5fc3ZtfQp3ZWlnaHRfdHJhaW4gPC0gcmVwKE5BLCBsZW5ndGgobGFiZWxfdHJhaW4pKQpmb3IgKHYgaW4gdW5pcXVlKGxhYmVsX3RyYWluKSl7CiAgd2VpZ2h0X3RyYWluW2xhYmVsX3RyYWluID09IHZdID0gMC41ICogbGVuZ3RoKGxhYmVsX3RyYWluKSAvIGxlbmd0aChsYWJlbF90cmFpbltsYWJlbF90cmFpbiA9PSB2XSkKfQppZiAoc2FtcGxlLnJld2VpZ2h0KXsKICB0bV90cmFpbl9zdm0gPC0gc3lzdGVtLnRpbWUoZml0X3RyYWluX3N2bSA8LXN2bV90cmFpbihmZWF0dXJlX3RyYWluLCBsYWJlbF90cmFpbiwgdyA9IHdlaWdodF90cmFpbiwgYmVzdF9jb3N0KSkKfSBlbHNlIHsKICB0bV90cmFpbl9zdm0gPC0gc3lzdGVtLnRpbWUoZml0X3RyYWluX3N2bSA8LXN2bV90cmFpbihmZWF0dXJlX3RyYWluLCBsYWJlbF90cmFpbiwgdyA9IE5VTEwsIGJlc3RfY29zdCkpCn0Kc2F2ZShmaXRfdHJhaW5fc3ZtLCBmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluX3N2bS5SRGF0YSIpCmBgYAoKCisgVHJhaW4gdGhlIFhHQm9vc3QgbW9kZWwgd2l0aCBvcHRpbWFsIHBhcmFtZXRlcnMKCmBgYHtyIGZpbmFsX3RyYWluX3hnYn0KCnhnYl90cmFpbl90aW1lIDwtIHN5c3RlbS50aW1lKGZpdF90cmFpbl94Z2IgPC0geGdib29zdF90cmFpbihmZWF0dXJlcyA9IGZlYXR1cmVfdHJhaW4sIGxhYmVscyA9IGxhYmVsX3RyYWluX3hnYiwgcGFyYW1zID0gbmV3X3BhcmFtcywgcm91bmRzID0gIHNldF9yb3VuZHMpKQoKc2F2ZShmaXRfdHJhaW5feGdiLCBmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluX3hnYi5SRGF0YSIpCgpgYGAKCisgVHJhaW4gdGhlIFJhbmRvbSBGb3Jlc3QgbW9kZWwgd2l0aCBvcHRpbWFsIHBhcmFtZXRlcnMKCmBgYHtyIGZpbmFsX3RyYWluX3JmfQpsb2FkKGZpbGUgPSAiLi4vb3V0cHV0L3Jlc19vb2JfcmYuUkRhdGEiKQpyZl90cmFpbl90aW1lIDwtIHN5c3RlbS50aW1lKGZpdF90cmFpbl9yYW5kb21mb3Jlc3QgPC0gcmZfdHJhaW4oZmVhdHVyZV90cmFpbiA9IGZlYXR1cmVfdHJhaW4sIGxhYmVsX3RyYWluID0gbGFiZWxfdHJhaW4sIG10cnkgPSByZl9vcHRfdHJlZSRtdHJ5LCBudHJlZSA9IHJmX29wdF90cmVlJG50cmVlKSkKc2F2ZShmaXRfdHJhaW5fcmFuZG9tZm9yZXN0LCBmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluX3JmLlJEYXRhIikKYGBgCgorIHRyYWluIFBDQSArIExEQQpgYGB7ciBQQ0F9CgojUENBIGZvciB0cmFpbmluZyBmZWF0dXJlcwpzb3VyY2UoIi4uL2xpYi90cmFpbl9QQ0EuUiIpCgojIG1ha2UgZGF0X3RyYWluIGEgbnVtZXJpYyBkYXRhIGZyYW1lCmRhdF90cmFpbi5uZXcgPC0gbWF0cml4KDAsIG5jb2wgPSBuY29sKGRhdF90cmFpbikgLSAxLCBucm93ID0gbnJvdyhkYXRfdHJhaW4pKQpmb3IgKGkgaW4gMToobmNvbChkYXRfdHJhaW4pIC0gMSkpIHsKZGF0X3RyYWluLm5ld1ssaV0gPC0gYXMubnVtZXJpYyhkYXRfdHJhaW5bW2ldXSkKfQpkYXRfdHJhaW4ubmV3IDwtIGFzLmRhdGEuZnJhbWUoZGF0X3RyYWluLm5ldykKCiNQQ0EgZm9yIHRyYWluaW5nIGZlYXR1cmVzCnRtX3RyYWluX3BjYSA8LSBzeXN0ZW0udGltZShmaXRfdHJhaW5fcGNhIDwtIHRyYWluX3BjYShkYXRfdHJhaW4ubmV3KSkKCnNhdmUoZml0X3RyYWluX3BjYSwgZmlsZT0iLi4vb3V0cHV0L3BjYV90cmFpbi5SRGF0YSIpCgojIGRldGVybWluZSB0aGUgaW1wb3J0YW50IHByaW5jaXBsZSBjb21wb25lbnRzCnNjcmVlcGxvdChmaXRfdHJhaW5fcGNhLCB0eXBlID0gImwiKQoKIyBUaGUgcHJvcG9ydGlvbiBvZiB2YXJpYW5jZSBmb3IgZmlyc3QgMzAwIFBDcwpzdW0oKGZpdF90cmFpbl9wY2Ekc2RldlsxOjMwMF0pXjIpIC8gc3VtKChmaXRfdHJhaW5fcGNhJHNkZXYpXjIpCgpgYGAKVGhlcmVmb3JlLCB3ZSBjYW4gY2hvb3NlIHRoZSBmaXJzdCAzMDAgcHJpbmNpcGxlIGNvbXBvbmVudHMgYmVjYXVzZSB0aGV5IGV4cGxhaW4gbW9zdCBvZiB0aGUgdG90YWwgdmFyaWFuY2UsIHdoaWNoIGlzIGFyb3VuZCA5OS45JS4KCmBgYHtyIFBDQSB0cmFpbn0KIyBnZXQgdGhlIGZlYXR1cmVzIG9mIHByaW5jaXBsZSBjb21wb25lbnRzIHdpdGggZW1vdGlvbiBpbmRleApkYXRfdHJhaW5fcGNhIDwtIGRhdGEuZnJhbWUoZml0X3RyYWluX3BjYSR4WywxOjMwMF0sIGVtb3Rpb25faWR4ID0gZGF0X3RyYWluWyw2MDA3XSkKYGBgCgpVc2UgdHJhaW5lZCBQQ0EgbW9kZWwgdG8gdGVzdCBkYXRhCmBgYHtyIFBDQSB0ZXN0fQpzb3VyY2UoIi4uL2xpYi90ZXN0X1BDQS5SIikKZGF0X3Rlc3QubmV3IDwtIGRhdF90ZXN0CmNvbG5hbWVzKGRhdF90ZXN0Lm5ldykgPC0gYyhjb2xuYW1lcyhkYXRfdHJhaW4ubmV3KSwgImVtb3Rpb25faWR4IikKCnRtX3Rlc3RfcGNhIDwtIHN5c3RlbS50aW1lKGRhdF90ZXN0Lm5ldyA8LSB0ZXN0X3BjYShmaXRfdHJhaW5fcGNhLCBkYXRfdGVzdC5uZXcpKQoKI2ZlYXR1cmVzIG9mIHRlc3RpbmcgcHJpbmNpcGxlIGNvbXBvbmVudHMgd2l0aCB0aGUgZW1vdGlvbiBpbmRleApkYXRfdGVzdF9wY2EgPC0gZGF0YS5mcmFtZShkYXRfdGVzdC5uZXdbLDE6MzAwXSwgZW1vdGlvbl9pZHggPSBkYXRfdGVzdFssNjAwN10pCgpzYXZlKGRhdF90cmFpbl9wY2EsIGZpbGU9Ii4uL291dHB1dC9mZWF0dXJlX3RyYWluX3BjYS5SRGF0YSIpCnNhdmUoZGF0X3Rlc3RfcGNhLCBmaWxlPSIuLi9vdXRwdXQvZmVhdHVyZV90ZXN0X3BjYS5SRGF0YSIpCmBgYAoKQXBwbHkgTERBIG1vZGVsCgpgYGB7ciBMREEgdHJhaW59CiNsb2FkKCIuLi9vdXRwdXQvZmVhdHVyZV90cmFpbl9wY2EuUkRhdGEiKQoKI3RyYWluIExEQSBtb2RlbApzb3VyY2UoIi4uL2xpYi90cmFpbl9MREEuUiIpCnRtX3RyYWluX2xkYSA8LSBzeXN0ZW0udGltZShmaXRfdHJhaW5fbGRhIDwtIHRyYWluX2xkYShkYXRfdHJhaW5fcGNhKSkKc2F2ZShmaXRfdHJhaW5fbGRhLCBmaWxlPSIuLi9vdXRwdXQvTERBX3RyYWluLlJEYXRhIikKCiN0cmFpbmluZyBhY2N1cmFjeSBpbiBMREEgbW9kZWwKc291cmNlKCIuLi9saWIvdGVzdF9MREEuUiIpCnByZWRfdHJhaW5fbGRhIDwtIHRlc3RfbGRhKGZpdF90cmFpbl9sZGEsIGRhdF90cmFpbl9wY2EpCmFjY3VfdHJhaW5fbGRhIDwtIG1lYW4ocHJlZF90cmFpbl9sZGEgPT0gZGF0X3RyYWluX3BjYSRlbW90aW9uX2lkeCkKCmNhdCgiVGhlIHRyYWluaWcgYWNjdXJhY3kgb2YgbW9kZWwgTERBIiwgImlzIiwgYWNjdV90cmFpbl9sZGEqMTAwLCAiJS5cbiIpCmNhdCgiVGltZSBmb3IgdHJhaW5pbmcgbW9kZWwgTERBID0gIiwgdG1fdHJhaW5fbGRhWzFdLCAicyBcbiIpCmBgYAoKCiMjIyBTdGVwIDU6IFJ1biB0ZXN0IG9uIHRlc3QgaW1hZ2VzCgorQmFzZWxpbmUgbW9kZWwKYGBge3IgdGVzdCBnYm19CnRtX3Rlc3RfYmFzZWxpbmUgPSBOQQpmZWF0dXJlX3Rlc3QgPC0gYXMubWF0cml4KGRhdF90ZXN0WywgLTYwMDddKQppZihydW4udGVzdCl7CiAgbG9hZChmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluX2Jhc2VsaW5lLlJEYXRhIikKICB0bV90ZXN0X2Jhc2VsaW5lIDwtIHN5c3RlbS50aW1lKHtsYWJlbF9wcmVkX2Jhc2VsaW5lIDwtIGFzLmludGVnZXIodGVzdF9nYm0oZml0X3RyYWluX2Jhc2VsaW5lLGZlYXR1cmVfdGVzdCxiZXN0X24udHJlZXMsIGJlc3Rfc2hyaW5rYWdlLCBwcmVkLnR5cGUgPSAnbGluaycpKTsgCiAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvYl9wcmVkX2Jhc2VsaW5lIDwtIHRlc3RfZ2JtKGZpdF90cmFpbl9iYXNlbGluZSwgZmVhdHVyZV90ZXN0LGJlc3Rfbi50cmVlcywgYmVzdF9zaHJpbmthZ2UsIHByZWQudHlwZSA9ICdyZXNwb25zZScpfSkKfQpgYGAKCitTVk0gbW9kZWwKYGBge3IgdGVzdCBzdm19CnRtX3Rlc3QgPSBOQQpmZWF0dXJlX3Rlc3QgPC0gYXMubWF0cml4KGRhdF90ZXN0WywgLTYwMDddKQppZihydW4udGVzdC5zdm0pewogIGxvYWQoZmlsZT0iLi4vb3V0cHV0L2ZpdF90cmFpbl9zdm0uUkRhdGEiKQogIHRtX3Rlc3Rfc3ZtIDwtIHN5c3RlbS50aW1lKHsKICAgIGxhYmVsX3ByZWRfc3ZtIDwtIGFzLmludGVnZXIoc3ZtX3Rlc3QoZml0X3RyYWluX3N2bSwgZmVhdHVyZV90ZXN0LCBwcmVkLnR5cGUgPSAnY2xhc3MnKSk7IAogICAgcHJvYl9wcmVkX3N2bSA8LSBzdm1fdGVzdChmaXRfdHJhaW5fc3ZtLCBmZWF0dXJlX3Rlc3QsIHByZWQudHlwZSA9ICdyZXNwb25zZScpfSkKfQpgYGAKCitYR0Jvb3N0CmBgYHtyIHRlc3QgeGdib29zdH0KdG1fdGVzdF94Z2IgPSBOQQpmZWF0dXJlX3Rlc3QgPC0gYXMubWF0cml4KGRhdF90ZXN0WywgLTYwMDddKQppZihydW4udGVzdCl7CiAgbG9hZChmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluX3hnYi5SRGF0YSIpCiAgdG1fdGVzdF94Z2IgPC0gc3lzdGVtLnRpbWUoe2xhYmVsX3ByZWRfeGdiIDwtIHByZWRpY3QoZml0X3RyYWluX3hnYiwgZmVhdHVyZV90ZXN0LCBwcmVkLnR5cGUgPSAnY2xhc3MnKTsKICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbF9wcmVkX3hnYltsYWJlbF9wcmVkX3hnYiA+PSAwLjVdIDwtIDE7CiAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxfcHJlZF94Z2JbbGFiZWxfcHJlZF94Z2IgPCAwLjVdIDwtICAwOwogICAgICAgICAgICAgICAgICAgICAgICAgIHByb2JfcHJlZF94Z2IgPC0gcHJlZGljdChmaXRfdHJhaW5feGdiLCBmZWF0dXJlX3Rlc3QsIHByZWQudHlwZSA9ICdyZXNwb25zZScpfSkKfQpgYGAKCitSYW5kb20gRm9yZXN0CmBgYHtyIHRlc3QgcmFuZG9tIGZvcmVzdCB9CnRtX3Rlc3RfcmYgPSBOQQpmZWF0dXJlX3Rlc3QgPC0gYXMubWF0cml4KGRhdF90ZXN0WywgLTYwMDddKQppZihydW4udGVzdCl7CiAgbG9hZChmaWxlPSIuLi9vdXRwdXQvZml0X3RyYWluX3JmLlJEYXRhIikKICB0bV90ZXN0X3JmIDwtIHN5c3RlbS50aW1lKHtsYWJlbF9wcmVkX3JmIDwtIHByZWRpY3QoZml0X3RyYWluX3JhbmRvbWZvcmVzdCwgZmVhdHVyZV90ZXN0LCBwcmVkLnR5cGUgPSAnY2xhc3MnKTsKICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9iX3ByZWRfcmYgPC0gcHJlZGljdChmaXRfdHJhaW5fcmFuZG9tZm9yZXN0LCBmZWF0dXJlX3Rlc3QsIHR5cGUgPSAncHJvYicpfSkKfSAKCmBgYAoKK0xEQQpgYGB7cn0Kc291cmNlKCIuLi9saWIvdGVzdF9MREEuUiIpCmxvYWQoZmlsZSA9ICIuLi9vdXRwdXQvZmVhdHVyZV90ZXN0X3BjYS5SRGF0YSIpCmxvYWQoZmlsZT0iLi4vb3V0cHV0L0xEQV90cmFpbi5SRGF0YSIpCnRtX3Rlc3RfbGRhIDwtIHN5c3RlbS50aW1lKHByZWRfbGRhIDwtIHRlc3RfbGRhKGZpdF90cmFpbl9sZGEsIGRhdF90ZXN0X3BjYSkpCgpgYGAKCgojIyMgRXZhbHVhdGlvbgoqQmFzZWxpbmUgTW9kZWwKYGBge3IgYmFzZWxpbmV9CiMjIHJld2VpZ2h0IHRoZSB0ZXN0IGRhdGEgdG8gcmVwcmVzZW50IGEgYmFsYW5jZWQgbGFiZWwgZGlzdHJpYnV0aW9uCmxhYmVsX3Rlc3QgPC0gYXMuaW50ZWdlcihkYXRfdGVzdCRsYWJlbCkKd2VpZ2h0X3Rlc3QgPC0gcmVwKE5BLCBsZW5ndGgobGFiZWxfdGVzdCkpCmZvciAodiBpbiB1bmlxdWUobGFiZWxfdGVzdCkpewogIHdlaWdodF90ZXN0W2xhYmVsX3Rlc3QgPT0gdl0gPSAwLjUgKiBsZW5ndGgobGFiZWxfdGVzdCkgLyBsZW5ndGgobGFiZWxfdGVzdFtsYWJlbF90ZXN0ID09IHZdKQp9CgphY2N1X2Jhc2VsaW5lIDwtIG1lYW4obGFiZWxfcHJlZF9iYXNlbGluZSA9PSBsYWJlbF90ZXN0KQp0cHIuZnByLmJhc2VsaW5lIDwtIFdlaWdodGVkUk9DKHByb2JfcHJlZF9iYXNlbGluZSwgbGFiZWxfdGVzdCwgd2VpZ2h0X3Rlc3QpCmF1Y19iYXNlbGluZSA8LSBXZWlnaHRlZEFVQyh0cHIuZnByLmJhc2VsaW5lKQoKCmNhdCgiVGhlIGFjY3VyYWN5IG9mIG1vZGVsIEdCTTogd2l0aCBuLnRyZWVzPSIsYmVzdF9uLnRyZWVzLCJhbmQgc2hyaW5rYWdlID0iLCBiZXN0X3Nocmlua2FnZSwgImlzIiwgYWNjdV9iYXNlbGluZSoxMDAsICIlLlxuIikKY2F0KCJUaGUgQVVDIG9mIG1vZGVsIEdCTTogd2l0aCBuLnRyZWVzPSIsIGJlc3Rfbi50cmVlcywiYW5kIHNocmlua2FnZSA9IiwgYmVzdF9zaHJpbmthZ2UsICJpcyIsIGF1Y19iYXNlbGluZSwgIi5cbiIpCgoKYGBgCgoKKlNWTQpgYGB7ciBldmFsdXRhdGlvbl9TVk19CmxhYmVsX3Rlc3QgPC0gYXMuaW50ZWdlcihkYXRfdGVzdCRsYWJlbCkKd2VpZ2h0X3Rlc3QgPC0gcmVwKE5BLCBsZW5ndGgobGFiZWxfdGVzdCkpCmZvciAodiBpbiB1bmlxdWUobGFiZWxfdGVzdCkpewogIHdlaWdodF90ZXN0W2xhYmVsX3Rlc3QgPT0gdl0gPSAwLjUgKiBsZW5ndGgobGFiZWxfdGVzdCkgLyBsZW5ndGgobGFiZWxfdGVzdFtsYWJlbF90ZXN0ID09IHZdKQp9CgphY2N1X3N2bSA8LSBtZWFuKGxhYmVsX3ByZWRfc3ZtID09IGxhYmVsX3Rlc3QpCnRwci5mcHIuc3ZtIDwtIFdlaWdodGVkUk9DKHByb2JfcHJlZF9zdm0sIGxhYmVsX3Rlc3QsIHdlaWdodF90ZXN0KQphdWNfc3ZtIDwtIFdlaWdodGVkQVVDKHRwci5mcHIuc3ZtKQpjYXQoIlRoZSBhY2N1cmFjeSBvZiBtb2RlbDoiLCBtb2RlbF9sYWJlbHNfc3ZtW3doaWNoLm1pbihyZXNfY3Zfc3ZtJG1lYW5fZXJyb3IpXSwgImlzIiwgYWNjdV9zdm0qMTAwLCAiJS5cbiIpCmNhdCgiVGhlIEFVQyBvZiBtb2RlbDoiLCBtb2RlbF9sYWJlbHNfc3ZtW3doaWNoLm1pbihyZXNfY3Zfc3ZtJG1lYW5fZXJyb3IpXSwgImlzIiwgYXVjX3N2bSwgIi5cbiIpCgoKYGBgCgoqWEdCb29zdApgYGB7ciBldmFsdWF0aW9uX1hHQn0KbGFiZWxfdGVzdCA8LSBhcy5pbnRlZ2VyKGRhdF90ZXN0JGxhYmVsKQpsYWJlbF90ZXN0X3hnYiA8LSBsYWJlbF90ZXN0CmxhYmVsX3Rlc3RfeGdiW2xhYmVsX3Rlc3RfeGdiPT0yXSA9IDAKYWNjdV94Z2IgPC0gbWVhbigobGFiZWxfcHJlZF94Z2IgPT0gbGFiZWxfdGVzdF94Z2IpKQp0cHIuZnByX3hnYiA8LSBXZWlnaHRlZFJPQyhwcm9iX3ByZWRfeGdiLCBsYWJlbF90ZXN0X3hnYikKYXVjX3hnYiA8LSBXZWlnaHRlZEFVQyh0cHIuZnByX3hnYikKCgpjYXQoIlRoZSBhY2N1cmFjeSBvZiB0aGUgWEdCb29zdCBtb2RlbDoiLCAiaXMiLCBhY2N1X3hnYioxMDAsICIlLlxuIikKY2F0KCJUaGUgQVVDIG9mIHRoZSBYR0Jvb3N0IG1vZGVsOiIsICJpcyIsIGF1Y194Z2IsICIuXG4iKQoKYGBgCgoqUmFuZG9tIEZvcmVzdApgYGB7ciBldmFsdWF0aW9uX3JmfQpsYWJlbF90ZXN0IDwtIGFzLmludGVnZXIoZGF0X3Rlc3QkbGFiZWwpCmFjY3VfcmYgPC0gbWVhbihsYWJlbF9wcmVkX3JmID09IGxhYmVsX3Rlc3QpCnRwci5mcHIucmYgPC0gV2VpZ2h0ZWRST0MocHJvYl9wcmVkX3JmWywyXSwgbGFiZWxfdGVzdCkKYXVjX3JmIDwtIFdlaWdodGVkQVVDKHRwci5mcHIucmYpCgoKY2F0KCJUaGUgYWNjdXJhY3kgb2YgdGhlIFJhbmRvbSBGb3Jlc3QgbW9kZWw6IiwgImlzIiwgYWNjdV9yZioxMDAsICIlLlxuIikKY2F0KCJUaGUgQVVDIG9mIHRoZSBSYW5kb20gRm9yZXN0IG1vZGVsOiIsICJpcyIsIGF1Y19yZiwgIi5cbiIpCmBgYAoKKkxEQQpgYGB7ciBldmFsdWF0aW9uX1BDQStMREF9CmFjY3VfbGRhIDwtIG1lYW4oZGF0X3Rlc3RfcGNhJGVtb3Rpb25faWR4ID09IHByZWRfbGRhKQpsYWJlbF9sZGEgPC0gYXMubnVtZXJpYyhkYXRfdGVzdF9wY2EkZW1vdGlvbl9pZHgpCnRwci5mcHIubGRhIDwtIFdlaWdodGVkUk9DKGFzLm51bWVyaWMocHJlZF9sZGEpLCBsYWJlbF9sZGEpCmF1Y19sZGEgPC0gV2VpZ2h0ZWRBVUModHByLmZwci5sZGEpCgpjYXQoIlRoZSBhY2N1cmFjeSBvZiB0aGUgUENBK0xEQSBtb2RlbDoiLCAiaXMiLCBhY2N1X2xkYSoxMDAsICIlLlxuIikKY2F0KCJUaGUgQVVDIG9mIHRoZSBQQ0ErTERBIG1vZGVsOiIsICJpcyIsIGF1Y19sZGEsICIuXG4iKQpgYGAKCiMjIyBTdW1tYXJpemUgUnVubmluZyBUaW1lClByZWRpY3Rpb24gcGVyZm9ybWFuY2UgbWF0dGVycywgc28gZG9lcyB0aGUgcnVubmluZyB0aW1lcyBmb3IgY29uc3RydWN0aW5nIGZlYXR1cmVzIGFuZCBmb3IgdHJhaW5pbmcgdGhlIG1vZGVsLCBlc3BlY2lhbGx5IHdoZW4gdGhlIGNvbXB1dGF0aW9uIHJlc291cmNlIGlzIGxpbWl0ZWQuIAoKKkJhc2VsaW5lIE1vZGVsCmBgYHtyIHJ1bm5pbmdfdGltZV9iYXNlbGluZX0KY2F0KCJUaW1lIGZvciBjb25zdHJ1Y3RpbmcgdHJhaW5pbmcgZmVhdHVyZXM9IiwgdG1fZmVhdHVyZV90cmFpblsxXSwgInMgXG4iKQpjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0ZXN0aW5nIGZlYXR1cmVzPSIsIHRtX2ZlYXR1cmVfdGVzdFsxXSwgInMgXG4iKQpjYXQoIlRpbWUgZm9yIHRyYWluaW5nIG1vZGVsPSIsIHRtX3RyYWluX2Jhc2VsaW5lWzFdLCAicyBcbiIpIApjYXQoIlRpbWUgZm9yIHRlc3RpbmcgbW9kZWw9IiwgdG1fdGVzdF9iYXNlbGluZVsxXSwgInMgXG4iKQpgYGAKCipTVk0KYGBge3IgcnVubmluZ190aW1lX1NWTX0KY2F0KCJUaW1lIGZvciBjb25zdHJ1Y3RpbmcgdHJhaW5pbmcgZmVhdHVyZXM9IiwgdG1fZmVhdHVyZV90cmFpblsxXSwgInMgXG4iKQpjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0ZXN0aW5nIGZlYXR1cmVzPSIsIHRtX2ZlYXR1cmVfdGVzdFsxXSwgInMgXG4iKQpjYXQoIlRpbWUgZm9yIHRyYWluaW5nIG1vZGVsPSIsIHRtX3RyYWluX3N2bVsxXSwgInMgXG4iKSAKY2F0KCJUaW1lIGZvciB0ZXN0aW5nIG1vZGVsPSIsIHRtX3Rlc3Rfc3ZtWzFdLCAicyBcbiIpCmBgYAoKKlhHQm9vc3QKYGBge3IgcnVubmluZ190aW1lX1hHQn0KY2F0KCJUaW1lIGZvciBjb25zdHJ1Y3RpbmcgdHJhaW5pbmcgZmVhdHVyZXM9IiwgdG1fZmVhdHVyZV90cmFpblsxXSwgInMgXG4iKQpjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0ZXN0aW5nIGZlYXR1cmVzPSIsIHRtX2ZlYXR1cmVfdGVzdFsxXSwgInMgXG4iKQpjYXQoIlRpbWUgZm9yIHRyYWluaW5nIG1vZGVsPSIsIHhnYl90cmFpbl90aW1lWzFdLCAicyBcbiIpIApjYXQoIlRpbWUgZm9yIHRlc3RpbmcgbW9kZWw9IiwgdG1fdGVzdF94Z2JbMV0sICJzIFxuIikKCmBgYAoKKlJhbmRvbSBGb3Jlc3QKYGBge3IgcnVubmluZ190aW1lX3JmfQpjYXQoIlRpbWUgZm9yIGNvbnN0cnVjdGluZyB0cmFpbmluZyBmZWF0dXJlcz0iLCB0bV9mZWF0dXJlX3RyYWluWzFdLCAicyBcbiIpCmNhdCgiVGltZSBmb3IgY29uc3RydWN0aW5nIHRlc3RpbmcgZmVhdHVyZXM9IiwgdG1fZmVhdHVyZV90ZXN0WzFdLCAicyBcbiIpCmNhdCgiVGltZSBmb3IgdHJhaW5pbmcgcmFuZG9tIGZvcmVzdCBtb2RlbD0iLCByZl90cmFpbl90aW1lWzFdLCAicyBcbiIpCmNhdCgiVGltZSBmb3IgdGVzdGluZyByYW5kb20gZm9yZXN0IG1vZGVsPSIsIHRtX3Rlc3RfcmZbMV0sICJzIFxuIikKYGBgCgoqIFBDQStMREEKYGBge3IgcnVubmluZ190aW1lX1BDQStMREF9CmNhdCgiVGltZSBmb3IgY29uc3RydWN0aW5nIHRyYWluaW5nIGZlYXR1cmVzPSIsIHRtX2ZlYXR1cmVfdHJhaW5bMV0sICJzIFxuIikKY2F0KCJUaW1lIGZvciBjb25zdHJ1Y3RpbmcgdGVzdGluZyBmZWF0dXJlcz0iLCB0bV9mZWF0dXJlX3Rlc3RbMV0sICJzIFxuIikKCmNhdCgiVGltZSBmb3IgdHJhaW5pbmcgUENBID0iLCB0bV90cmFpbl9wY2FbMV0sICJzIFxuIikKY2F0KCJUaW1lIGZvciB0ZXN0aW5nIFBDQSA9IiwgdG1fdGVzdF9wY2FbMV0sICJzIFxuIikKY2F0KCJUaW1lIGZvciB0cmFpbmluZyBtb2RlbCBMREEgPSAiLCB0bV90cmFpbl9sZGFbMV0sICJzIFxuIikKY2F0KCJUaW1lIGZvciB0ZXN0aW5nIG1vZGVsIExEQSA9ICIsdG1fdGVzdF9sZGFbMV0sICJzIFxuIikKYGBgCiMjI1JlZmVyZW5jZQotIER1LCBTLiwgVGFvLCBZLiwgJiBNYXJ0aW5leiwgQS4gTS4gKDIwMTQpLiBDb21wb3VuZCBmYWNpYWwgZXhwcmVzc2lvbnMgb2YgZW1vdGlvbi4gUHJvY2VlZGluZ3Mgb2YgdGhlIE5hdGlvbmFsIEFjYWRlbXkgb2YgU2NpZW5jZXMsIDExMSgxNSksIEUxNDU0LUUxNDYyLgoKCgoKCgoKCgoKCgoK